In this step of the tutorial you create a Kanzi Engine plugin in which you define a data source. You use the data source to provide the data to your application.
The starting point of this tutorial is stored in the <KanziWorkspace>/Tutorials/Data sources/Start directory:
The <KanziWorkspace>/Tutorials/Data sources/Completed directory contains the completed project of this tutorial.
To define a data source:
class XML_DATA_SOURCE_API XMLDataSource : public DataSource { public: KZ_METACLASS_BEGIN(XMLDataSource, DataSource, "CustomDataSourceType") KZ_METACLASS_END() ... };
class XML_DATA_SOURCE_API XMLDataSource : public DataSource { public: // Create the XmlFilename property type. You use this property type to tell the data source plugin which XML file to read. static PropertyType<string> XmlFilenameProperty; KZ_METACLASS_BEGIN(XMLDataSource, DataSource, "XML_data_source") // Add the property you created to the class metadata. KZ_METACLASS_PROPERTY_TYPE(XmlFilenameProperty) KZ_METACLASS_END() ... };
class XML_DATA_SOURCE_API XMLDataSource : public DataSource { public: ... // Add functions for loading and unloading the data source resource. virtual void loadFromKZB(const ResourceLoaderThreadContext* threadContext, KzcInputStream* inputStream, const KzuBinaryFileInfo* file) KZ_OVERRIDE; virtual void unloadOverride() KZ_OVERRIDE; ... };
initialize()
function create the helper function for loading the XML file and add the function for opening the XML file after loading the data source.class XML_DATA_SOURCE_API XMLDataSource : public DataSource { ... protected: ... // Create the helper function for loading the XML file. void loadXmlFile(const char* filename); // Add the function for opening the XML file after loading the data source. virtual void loadFromKZB(Domain* domain, KzbFile& kzbFile, ReadOnlyMemoryFile& file, KzbMemoryParser& parser) KZ_OVERRIDE; ... };
// Provides the functionality to process XML. #include "tinyxml2.h"
// Define the metadata for the property type you use to tell the data source plugin which XML file to read.
PropertyType<string> XMLDataSource::XmlFilenameProperty(kzMakeFixedString("XMLdatasource.XMLDataSourceFile"), "", 0, false,
KZ_DECLARE_EDITOR_METADATA
(
// Set the name of the property the way it is shown in Kanzi Studio.
metadata.displayName = "XML Data Source File";
// Set the tooltip for the property.
metadata.tooltip = "Sets which XML file the data source plugin reads.";
// Select the editor which is used to edit the value of this property type.
// BrowseFileTextEditor editor contains a text box with a Browse button next to it.
metadata.editor = "BrowseFileTextEditor";
));
// Add a data object of the type specified by the type attribute in the XML element. Get the initial value from the text parameter. DataObjectSharedPtr addDataObject(Domain *domain, const char* type, const char* name, const char* text) { shared_ptr<DataObject> object; // Create an integer data object from the int type attributes. if (type && strcmp(type, "int") == 0) { int value = 0; if (text) { value = atoi(text); } object = make_shared<DataObjectInt>(domain, name, value); } // Create a float data object from the float and real type attributes. else if (type && (strcmp(type, "real") == 0 || strcmp(type, "float") == 0)) { double value = 0; if (text) { value = atof(text); } object = make_shared<DataObjectReal>(domain, name, value); } // Create a Boolean data object from the bool and boolean type attributes. else if (type && (strcmp(type, "bool") == 0 || strcmp(type, "boolean") == 0)) { bool value = false; if (text) { value = (strcmp(text, "true") == 0); } object = make_shared<DataObjectBool>(domain, name, value); } // Create a string data object from the string type attributes. else if (type && strcmp(type, "string") == 0) { string value; if (text) { value = text; } object = make_shared<DataObjectString>(domain, name, value); } else { // If the type attribute is not set, create a generic data object. This is used to create the hierarchy in the data source. object = make_shared<DataObject>(domain, name); } return object; }
// This function converts the content of the XML structure in memory to data objects. You use these data objects to construct the data object tree of the data source. // The second parameter sets the node where this pass places new data objects. // The third parameter sets the pointer to the location where the conversion and parsing is progressing (within the XML in the memory, the child element in the XML). static void addDataObjectsRecursively(Domain* domain, DataObjectSharedPtr parent, const tinyxml2::XMLElement* xml) { // Check whether the current element in the XML file has the type attribute set. const tinyxml2::XMLAttribute* typeAttribute = xml->FindAttribute("type"); // Get value of the type attribute. const char* type = 0; if (typeAttribute) { type = typeAttribute->Value(); } // Create the data object based on the value of the type attribute. DataObjectSharedPtr object = addDataObject(domain, type, xml->Name(), xml->GetText()); // Add the data object as a child to the parent data object. parent->addChild(object); // Traverse the tree in the XML file to add data objects for each child element of the current XML element. for (const tinyxml2::XMLElement* child = xml->FirstChildElement(); child; child = child->NextSiblingElement()) { // Recurse. addDataObjectsRecursively(domain, object, child); } }
// Create the function which loads the XML file from the disk to the memory.
void XMLDataSource::loadXmlFile(const char* filename)
{
// Clear the previous data object tree.
m_root.reset();
// Load the XML file.
// Introduce the XML to the memory.
tinyxml2::XMLDocument doc;
// Load the file from the disk to the memory.
tinyxml2::XMLError error;
unique_ptr<File> file(new ReadOnlyDiskFile(filename));
file->seek(File::SeekBegin, 0);
vector<char> data(static_cast<unsigned int>(file->size()));
file->read(data.data(), data.size());
// Parse the XML document from the memory and release the open file.
error = doc.Parse(data.data(), data.size());
file.reset();
// If the plugin successfully loads the file set in the XML Data Source File property, create data objects.
if (error == tinyxml2::XML_SUCCESS)
{
// Create the root node.
const tinyxml2::XMLElement* element = doc.RootElement();
// Create the root data object.
m_root = make_shared<DataObject>(getDomain(), "Root");
do
{
// Populate the rest of the nodes as the child data object nodes of the root node data object.
addDataObjectsRecursively(getDomain(), m_root, element);
// Handle all siblings of the root element.
element = element->NextSiblingElement();
} while (element);
}
}
// Load the data source resource and create the data object tree during the loading of the kzb file of the application.
void XMLDataSource::loadFromKZB(const ResourceLoaderThreadContext* threadContext, KzcInputStream* inputStream, const KzuBinaryFileInfo* file)
{
// Call the base class function for loading the kzb file.
DataSource::loadFromKZB(threadContext, inputStream, file);
// If you do not set the value of the XML Data Source File property, the plugin does not do anything.
string filename = getProperty(XmlFilenameProperty);
if (!filename.empty())
{
// Call the member function that loads the XML file.
loadXmlFile(filename.c_str());
}
}
unloadOverride()
function when you exit the application or reload the kzb files in your application.// Kanzi calls unloadOverride() when the application exits, or when you reload kzb files in your application.
void XMLDataSource::unloadOverride()
{
// Call the base class function for unloading.
DataSource::unloadOverride();
// Clean up the data objects used in the application.
m_root.reset();
}
// Open the XML file after loading the data source. void XMLDataSource::loadFromKZB(Domain* domain, KzbFile& kzbFile, ReadOnlyMemoryFile& file, KzbMemoryParser& parser) { DataSource::loadFromKZB(domain, kzbFile, file, parser); // If you do not set the value of the XML Data Source File property, the plugin does not do anything. string filename = getProperty(XmlFilenameProperty); if (!filename.empty()) { // Call the member function that loads the XML file. loadXmlFile(filename.c_str()); } }
This section demonstrates how you can update a data source by checking every second whether the file you use as a source of data has changed. Note that this implementation works only on Windows. The correct way to refresh a data source depends on your target platform.
To update the data source:
create
function addpublic:
...
// Destructor
virtual ~XMLDataSource() KZ_OVERRIDE
{
// Stop the file tracking when the user renames the data source in a Kanzi Studio project.
stopFileTracking();
}
private: // Introduce the pointer to the root data object of the data source. DataObjectSharedPtr m_root;with
private: // Declare the function that starts the timer which tracks the changes in the file. void startFiletracking(const char* filename); // Declare the function that stops the timer which tracks the changes in the file. void stopFileTracking(); // Declare the timer callback function. void onTimer(const TimerMessageArguments& arguments); // Introduce the pointer to the root data object of the data source. DataObjectSharedPtr m_root; // Define a member variable for the previous timestamp of the file. time_t m_fileTime; // Define a member variable for the timer subscription token. TimerSubscriptionToken m_timerSubscription;
// Provides the system utility function to get the timestamp of the file. #include <sys/stat.h>
// Helper function to get the file modification time.
static time_t getFileModificationTime(const char* filename)
{
time_t result = 0;
struct stat fs;
if (stat(filename, &fs) == 0)
{
result = fs.st_mtime;
}
return result;
}
// Start the file tracking timer.
void XMLDataSource::startFiletracking(const char* filename)
{
stopFileTracking();
m_fileTime = getFileModificationTime(filename);
// Start the timer that sets off every second until you stop it, and calls the onTimer() callback function.
m_timerSubscription = addTimerHandler(getMessageDispatcher(), chrono::seconds(1), KZU_TIMER_MESSAGE_MODE_REPEAT_BATCH,
bind(&XMLDataSource::onTimer, this, placeholders::_1));
}
// Stop the file tracking timer.
void XMLDataSource::stopFileTracking()
{
if (m_timerSubscription)
{
// Remove the timer.
removeTimerHandler(getMessageDispatcher(), m_timerSubscription);
// Clear the subscription handler.
m_timerSubscription.reset();
}
}
// Timer callback function.
void XMLDataSource::onTimer(const TimerMessageArguments& /*arguments*/)
{
string filename = getProperty(XmlFilenameProperty);
// If you do not set the value of the XML Data Source File property, the plugin does not do anything.
if (!filename.empty())
{
// Get the modification time.
time_t modificationTime = getFileModificationTime(filename.c_str());
// Compare the current and previous modification times. If they differ, update the file.
if (modificationTime != m_fileTime)
{
loadXmlFile(filename.c_str());
m_fileTime = modificationTime;
// Notify that the data source has changed. This assumes that everything has changed.
notifyModified();
}
}
}
loadXmlFile()
function to include the functionality for tracking the changes in the data source file.// Create the function which loads the XML file from disk to memory. void XMLDataSource::loadXmlFile(const char* filename) { stopFileTracking(); ... if (error == tinyxml2::XML_SUCCESS) { ... startFiletracking(filename); } ... }
XMLDataSource::unloadOverride()
function a call to the stopFileTracking()
function to stop the file tracking.void XMLDataSource::unloadOverride() { ... stopFileTracking(); }
After you are done creating the plugin, build the plugin .dll which you use in the next step of this tutorial in the Kanzi Studio project to get the data for your application from an XML file.
To build the plugin in Visual Studio in the Solution Explorer right-click the XML_data_source project and select Build.